<?php
/**
 * In this File the class '\UF\Type' is defined.
 *
 * @category   UniKap-Framework
 * @package    Core
 * @subpackage Helpers
 * @author     Ulf -UniKado- Kadner <ulfikado@gmail.com>
 * @since      2014-03-29 16:17
 * @version    0.1
 */

namespace UF
{

    /**
     * This class defines a object, to get + hold informations about a value and the type of the value.
     *
     * @since  v0.1
     */
    class Type implements \UF\IToStringConverter
    {

        # <editor-fold defaultstate="collapsed" desc="- - - -   C O N S T A N T S   - - - - - - - - - - - - - - - - - - - - - - - - - -">

        /**
         * Describes a PHP double value.
         *
         * @since  v0.1
         */
        const PHP_DOUBLE = 'double';

        /**
         * Describes a PHP float / single value.
         *
         * @since  v0.1
         */
        const PHP_FLOAT = 'float';

        /**
         * Describes a PHP integer value.
         *
         * @since  v0.1
         */
        const PHP_INTEGER = 'int';

        /**
         * PHP-Type der einen Boolean-Wert bezeichnet.
         *
         * @since  v0.1
         */
        const PHP_BOOLEAN = 'bool';

        /**
         * Describes a PHP string value.
         *
         * @since  v0.1
         */
        const PHP_STRING = 'string';

        /**
         * Describes a PHP NULL value.
         *
         * @since  v0.1
         */
        const PHP_NULL = 'null';

        /**
         * Describes a PHP resource value.
         *
         * @since  v0.1
         */
        const PHP_RESOURCE = 'resource';

        /**
         * Describes a PHP array value.
         *
         * @since  v0.1
         */
        const PHP_ARRAY = 'array';

        /**
         * Describes a PHP value with a other type.
         *
         * @since  v0.1
         */
        const PHP_UNKNOWN = 'unknown';

        # </editor-fold>

        # <editor-fold defaultstate="collapsed" desc="- - - -   P R I V A T E   F I E L D S   - - - - - - - - - - - - - - - - - - - - -">

        /**
         * @since  v0.1
         */
        private $value;
        /**
         * @since  v0.1
         */
        private $isStringConvertible;
        /**
         * @since  v0.1
         */
        private $stringValue;
        /**
         * @since  v0.1
         */
        private $typeName;
        /**
         * @since  v0.1
         */
        private $hash;

        # </editor-fold>

        # <editor-fold defaultstate="collapsed" desc="- - - >   C O N S T R U C T O R   - - - - - - - - - - - - - - - - - - - - - - - -">

        /**
         * Inits a new instance.
         *
         * @param mixed $value The value.
         * @since  v0.1
         */
        public function __construct( $value )
        {
            $this->value = $value;
            $this->isStringConvertible = \UF\TypeTool::IsStringConvertible( $value, $this->stringValue );
            if ( !$this->isStringConvertible ) $this->stringValue = null;
            if ( \is_null( $value ) )         $this->typeName = \UF\Type::PHP_NULL;
            elseif ( \is_string( $value ) )   $this->typeName = \UF\Type::PHP_STRING;
            elseif ( \is_array( $value ) )    $this->typeName = \UF\Type::PHP_ARRAY;
            elseif ( \is_object( $value ) )
            {
                if ( $value instanceof \UF\Type ) throw new \UF\Exception( \UF\_( '_core',
                    'Could not create \\UF\\Type-Instance of a \\UF\\Type-Value!' ) );
                                              $this->typeName = \get_class( $value );
            }
            elseif ( \is_int( $value ) )      $this->typeName = \UF\Type::PHP_INTEGER;
            elseif ( \is_double( $value ) )   $this->typeName = \UF\Type::PHP_DOUBLE;
            elseif ( \is_float( $value ) )    $this->typeName = \UF\Type::PHP_FLOAT;
            elseif ( \is_bool( $value ) )     $this->typeName = \UF\Type::PHP_BOOLEAN;
            elseif ( \is_resource( $value ) ) $this->typeName = \UF\Type::PHP_RESOURCE;
            else                              $this->typeName = \UF\Type::PHP_UNKNOWN;
        }

        # </editor-fold>

        # <editor-fold defaultstate="collapsed" desc="- - - -   P U B L I C   M E T H O D S   - - - - - - - - - - - - - - - - - - - - -">

        /**
         * Checks if the current value is equal to $value. If $strict is FALSE there is only checked, if the
         * value type is equal. Otherwise also the value self will be checked.
         *
         * @param  mixed $value The other value.
         * @param  bool  $strict Check strictly? (Defaults to FALSE)
         * @return bool  TRUE oder FALSE
         * @since  v0.1
         */
        public function equals( $value, $strict = false )
        {
            if ( !( $value instanceof \UF\Type ) ) $value = new \UF\Type( $value );
            $res = ( $value->typeName == $this->typeName );
            if ( !$res || !$strict ) return $res;
            if ( $value->isStringConvertible ) return ( $value->stringValue == $this->stringValue );
            return ( $value->value == $this->value );
        }

        /**
         * Returns the current value of the instance.
         *
         * @return mixed Value of this type.
         * @since  v0.1
         */
        public final function getValue()
        {
            return $this->value;
        }

        /**
         * Returns, if defined (see {@see \UF\Type::hasAssociatedString()}), the string, associated with the
         * current type value. (see {@see \UF\Type::getValue()}).
         *
         * @param  string $default Default value if no string value is avialable (Defaults to a empty string)
         * @return string
         * @since  v0.1
         */
        public final function getStringValue( $default='' )
        {
            if ( !$this->isStringConvertible ) return $default;
            return $this->stringValue;
        }

        /**
         * Returns if from current value a usable string representation can be used.
         *
         * @return bool
         * @since  v0.1
         */
        public final function hasAssociatedString()
        {
            return $this->isStringConvertible;
        }

        /**
         * Returns the name of the current value type.
         *
         * @return string
         * @since  v0.1
         */
        public final function getType()
        {
            return $this->typeName;
        }

        /**
         * Returns, if current value points to a PHP resource type.
         *
         * @return bool
         * @since  v0.1
         */
        public final function isResource()
        {
            return $this->typeName == \UF\Type::PHP_RESOURCE;
        }

        /**
         * Returns the Type value as string.
         *
         * @return string
         * @since  v0.1
         */
        public final function __toString()
        {
            return $this->stringValue;
        }

        /**
         * Returns the Type value as string.
         *
         * @return string
         * @since  v0.1
         */
        public final function toString()
        {
            return (string) $this;
        }

        /**
         * Returns the PHP code to define the current value.
         *
         * @return string
         * @since  v0.1
         */
        public final function getPhpCode()
        {
            $str = $this->stringValue;
            switch ( $this->typeName )
            {
                case \UF\Type::PHP_BOOLEAN: return ( $this->value ? 'true' : 'false' );
                case \UF\Type::PHP_DOUBLE:
                case \UF\Type::PHP_FLOAT:
                case \UF\Type::PHP_INTEGER: return $str;
                case \UF\Type::PHP_STRING:
                    if ( \preg_match( "~[\r\n\t]+~", $str ) ) {
                        $str = \str_replace(
                            array('\\',  "\r", "\n", "\t", "\0", '"',  '$'),
                            array('\\\\','\\r','\\n','\\t','\\0','\\"','\\$'), $str );
                        return '"' . $str . '"'; }
                    return "'" . \str_replace( array('\\',   "'"), array('\\\\', "\\'"), $str ) . "'";
                case \UF\Type::PHP_RESOURCE:
                case \UF\Type::PHP_NULL:
                case \UF\Type::PHP_UNKNOWN: return 'null';
                default:
                    $str = \serialize( $this->value );
                    if ( \preg_match( "~[\r\n\t]+~", $str ) )
                    {
                        $str = \str_replace(
                            array('\\',  "\r", "\n", "\t", "\0", '"',  '$'),
                            array('\\\\','\\r','\\n','\\t','\\0','\\"','\\$'),
                            $str );
                    }
                    else
                        $str = \str_replace(
                            array('\\',   '"',   '$'),
                            array('\\\\', '\\"', '\\$'),
                            $str );
                    return '\unserialize("' . $str . '")';
            }
        }

        /**
         * Implements the ability to clone the object instance.
         *
         * @return \UF\Type The clone
         * @since  v0.1
         */
        public function __clone()
        {
            $res = null;
            switch ($this->typeName)
            {
                case \UF\Type::PHP_BOOLEAN:
                case \UF\Type::PHP_DOUBLE:
                case \UF\Type::PHP_FLOAT:
                case \UF\Type::PHP_INTEGER:
                case \UF\Type::PHP_NULL:
                case \UF\Type::PHP_RESOURCE:
                case \UF\Type::PHP_STRING:
                    $res = new \UF\Type( $this->value );
                    break;
                default:
                    $val = clone( $this->value );
                    $res = new \UF\Type( $val );
                    break;
            }
            return $res;
        }

        # </editor-fold>

    }

}

